Javascript 是一種事件驅動 (Event-driven)
的程式語言,簡單來說就是當使用者對網頁做了一些動作才會觸發執行要做的下一個動作,也好比辦公室擺了一台電話在桌上,但是電話要是沒響,我們不會主動去「接電話」。
如果有一個按鈕被點擊後會出現對話框,那麼「點擊按鈕」這件事,就被稱作「事件」(Event)。而負責處理事件的程式通常被稱為「事件處理者」(Event Handler),也就是「啟動對話框的顯示」這個動作。
常見的事件(event)有點擊事件(click)
、滑鼠事件(mouse)
、鍵盤事件(keyboard)
…。
假設我今天有一個A裡面包含了一個B,這表示B也是A的一部分。
<div class="outer">A
<div class="inner">B</div>
</div>
這表示B也是A的一部分。當我們點擊了B的時候,也代表我們也點擊到A,可以說實際上我們也點擊到整個網頁。
而事件流程 (Event Flow) 指的就是「網頁元素接收事件的順序」。
事件流程分成兩種機制:
接下來會針對兩個機制分別做介紹。
在事件捕獲階段DOM
的事件會從最頂層 (document) 開始往下尋找目標 (target),這個過稱稱為事件捕獲。
所以這個機制的順序是由上層往下傳遞。拿上面的A跟B例子來講就是:
<document>
→<html>
→<body>
→<div class="outer">A</div>
→<div class="inner">B</div>
在事件冒泡階段DOM
的事件會從最底層元素節點開始往上傳遞到document
,這個過稱稱為事件冒泡。
所以這個機制的順序是由下層往上傳遞。一樣拿上面的A跟B例子來講就是:
<div class="inner">B</div>
→<div class="outer">A</div>
→<body>
→<html>
→<document>
那一定有人會好奇這樣子這樣子事件到底是依賴哪種機制執行? 答案是兩種機制都會執行!
如果要用上圖來解釋的話就是:
td
的 click 事件發生時,會先走紅色的 「capture phase」。順序如下:
Document
→html
→body
→table
→tbody
→tr
→td(實際被點擊的元素)
由上而下依序觸發它們的 click 事件。
接著會然後再繼續執行綠色的 「bubble phase」,反方向由 td
一路往上傳至 Document
,整個事件流程到此結束。
那如果我們要如何驗證上面的A跟B的例子? 我們可以透過 addEventListener()
方法來綁定 click
事件。
其實我們所熟知的 addEventListener
還有第三個參數:true/false 可以決定你的監聽要在捕獲階段或是冒泡階段觸發。
false
為監聽冒泡階段true
為監聽捕獲階段let parent = document.querySelector(".outer");
let child = document.querySelector(".inner");
parent.addEventListener("click",function () {
console.log("A Capturing");
},true);
parent.addEventListener("click",function () {
console.log("B Bubbling");
},false);
child.addEventListener("click",function () {
console.log("B Capturing");
},true);
child.addEventListener("click",function () {
console.log("B Bubbling");
},false);
如果我今天點到的是B(子元素)會出現:
A Capturing
B Capturing
B Bubbling
A Bubbling
如果我點到的是A(父元素)會出現:
A Capturing
B Bubbling
這樣就可以看的出來事件的傳遞機制是由父層的Capturing
→子層的Capturing
→點擊目標(target)
→子層的Bubbling
→父層的Bubbling
。
假設我想要再點到B的時候阻止事件傳遞,可以加入stopPropagation
child.addEventListener("click",function (e) {
console.log("B Capturing");
e.stopPropagation();
});
就會出現這樣的結果
A Capturing
B Capturing
但這還不是我們要的結果,還要加入
parent.addEventListener("click",function (e) {
console.log("A Capturing");
e.stopPropagation();
},true);
就可以得到這樣的結果
A Capturing
stopPropagation()
停止事件傳遞跟preventDefault()
停止預設行為很容易搞混。
stopPropagation()
停止事件傳遞是停止事件的往下繼續傳遞。
preventDefault()
停止預設行為
舉個例子來說a標籤
是超連結預設行為是跳轉頁面,用了preventDefault()
就可以停止預設行為不會被帶到另一個新的頁面了。
重新認識 JavaScript: Day 14 事件機制的原理
[JavaScript] Javascript 中的 DOM 事件傳遞機制:捕獲與冒泡 (capturing and bubbling)